package com.tmt.datatable.editor.ext.select2;

import com.tmt.annotation.*;
import com.tmt.annotation.parser.EnumAnnotationParser;
import com.tmt.annotation.parser.LazyLoadParser;
import com.tmt.datatable.model.FieldOption;
import com.tmt.helper.PackageHelper;
import com.tmt.helper.ReflectHelper;
import com.tmt.helper.StringHelper;
import org.springframework.util.StringUtils;

import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by ThreeManTeam on 2017/7/6.
 */
public class Select2OptionValueLoader {

    private static HashMap<String, ICustomSelect2OptionValueLoader> CustomSelect2OptionValueLoaderMap = new HashMap<>();

    /**
     * 查找data列表中的所有对象的带@Select2OptionValue注解的字段，并根据注解信息创建FieldOption数据。
     *
     * @param data
     * @param fieldOptionMapping
     * @param <T>
     */
    public static <T> void loadSelect2OptionValue(List<T> data, Map<String,
            List<IFieldOption>> fieldOptionMapping, List<ISelect2OptionMappedObj> select2OptionMappedObjs) {
        if (data == null || data.size() <= 0)
            return;

        data.forEach(x -> {
            List<Field> fieldList = ReflectHelper.findAllFieldByAnnotations(x.getClass(), Arrays.asList(Select2OptionValue.class, Select2OptionValues.class), -1);
            fieldList.forEach(y -> {
                //读取oneToMany注解对象。
                OneToMany oneToManyAnnotation = y.getAnnotation(OneToMany.class);

                List<Select2OptionValue> allAnnotations = new ArrayList<>();
                Select2OptionValue[] annotationArr = y.getAnnotationsByType(Select2OptionValue.class);
                if (annotationArr != null && annotationArr.length > 0) {
                    allAnnotations.addAll(Arrays.asList(annotationArr));
                }

                Select2OptionValues[] Select2OptionValuesArr = y.getAnnotationsByType(Select2OptionValues.class);
                if (Select2OptionValuesArr != null && Select2OptionValuesArr.length > 0) {
                    Arrays.stream(Select2OptionValuesArr).forEach(t -> allAnnotations.addAll(Arrays.asList(t.value())));
                }

                allAnnotations.stream().forEach(annotation -> {
                    //读取Select2Option的键值
                    String key = annotation.value();
                    //构造可选项列表
                    List<IFieldOption> fieldOptions = new ArrayList<>();
                    if (fieldOptionMapping.containsKey(key))
                        fieldOptions = fieldOptionMapping.get(key);
                    else
                        fieldOptionMapping.put(key, fieldOptions);

                    try {
                        //得到当前对象的字段值。
                        Object fieldValue = ReflectHelper.getPropObjectByPropPath(x, y.getName());
                        ;
                        //转向处理外部类属性映射处理模式。
                        if (!StringUtils.isEmpty(annotation.mappedClass())) {
                            if (fieldValue != null) {
                                loadSelect2OptionValueByMappedClass(fieldValue, annotation, fieldOptions, select2OptionMappedObjs);
                            }
                            return;
                        } else if (!StringUtils.isEmpty(annotation.loaderClass())) {
                            try {
                                Class clazz = Class.forName(annotation.loaderClass());
                                String packageName = getPackageName(x, annotation);
                                ICustomSelect2OptionValueLoader loader = getLoaderInstance(x, y.getName(), clazz, packageName);
                                if (loader.support(x.getClass(), y.getName())) {
                                    List<IFieldOption> tmpList = new ArrayList<>();
                                    loader.load(x, y.getName(), tmpList);
                                    if (!annotation.displayExtendInfo())
                                        tmpList.forEach(t -> t.setLabel(t.getLabel().split("\\(")[0]));

                                    List<IFieldOption> finalFieldOptions1 = fieldOptions;
                                    tmpList = tmpList.stream().filter(m -> finalFieldOptions1.stream().noneMatch(n -> n.getValue() == m.getValue()))
                                            .collect(Collectors.toList());
                                    fieldOptions.addAll(tmpList);
                                }
                            } catch (Exception ignored) {
                            }
                            return;
                        }

                        String idProp = null;
                        if (key.contains(".")) {
                            String[] infos = key.split("\\.");
                            idProp = infos[infos.length - 1].trim();
                        }

                        //支持x[].y.z模式的属性链
                        Collection<Object> objList = new ArrayList<>();
                        if (Collection.class.isAssignableFrom(fieldValue.getClass()) && key.contains(".")) {
                            String propPath = key.replace(y.getName(), "").replaceFirst("\\[]\\.", "");
                            if (propPath.contains("." + idProp)) {
                                String path = propPath.replace("." + idProp, "");
                                ((Collection) fieldValue).forEach(m -> {
                                    try {
                                        Object tmpObj = ReflectHelper.getPropObjectByPropPath(m, path);
                                        objList.add(tmpObj);
                                    } catch (Exception ex) {
                                    }
                                });
                            } else {
                                objList.addAll((Collection) fieldValue);
                            }
                        } else {
                            objList.add(fieldValue);
                        }

                        String finalIdProp = idProp;
                        List<IFieldOption> finalFieldOptions = fieldOptions;
                        objList.forEach(m -> {
                            //针对这个值提取字段可选项信息。
                            List<FieldOption> tmpfieldOptions = getFieldOptionItemsBy(x, y.getName(), m, finalIdProp,
                                    annotation.displayExtendInfo(), oneToManyAnnotation, null);
                            tmpfieldOptions.forEach(t -> {
                                //避免重复可选项
                                if (finalFieldOptions.stream().noneMatch(z -> Objects.equals(z.getText(), t.getText())
                                        && Objects.equals(z.getId(), t.getId())))
                                    finalFieldOptions.add(t);
                            });
                        });

                    } catch (Exception ex) {
                    }

                    //确定是否输出扩展信息。
                    if (!annotation.displayExtendInfo()) {
                        fieldOptions.forEach(z -> z.setLabel(z.getLabel().split("\\(")[0]));
                    }
                });

            });
        });
    }

    public static List<FieldOption> getFieldOptionItems(List<Object> list, boolean displayExtendInfo, OneToMany oneToManyAnnotation) {
        return getFieldOptionItems(list, null, displayExtendInfo, oneToManyAnnotation, null);
    }

    /**
     * 从字段的值对象上提取可选项信息。
     *
     * @param list
     * @param idProp              指定返回的FieldOption项的Value属性应该填入obj对象的哪个属性的值，默认是填入id属性的值。
     * @param displayExtendInfo   是否显示扩展信息。
     * @param oneToManyAnnotation 字段上的OneToMany注解对象。
     * @param filterConditions    前端传入参数及其值的集合。
     * @return
     */
    public static List<FieldOption> getFieldOptionItems(List<Object> list, String idProp,
                                                        boolean displayExtendInfo,
                                                        OneToMany oneToManyAnnotation,
                                                        Map<String, String[]> filterConditions) {
        List<FieldOption> fieldOptions = new ArrayList<>();
        if (list != null && list.size() > 0) {
            list.forEach(x -> fieldOptions.addAll(getFieldOptionItemsBy(null, null, x, idProp, displayExtendInfo, oneToManyAnnotation, filterConditions)));
        }
        return fieldOptions;
    }

    /**
     * 从字段的值对象上提取可选项信息。
     *
     * @param srcObj              正在处理的原对象。
     * @param srcPropName         正在处理的原对象的属性名称。
     * @param propValue           字段的值对象。
     * @param idProp              指定返回的FieldOption项的Value属性应该填入obj对象的哪个属性的值，默认是填入id属性的值。
     * @param displayExtendInfo   是否显示扩展信息。
     * @param oneToManyAnnotation 字段上的OneToMany注解对象。
     * @param filterConditions    前端传入参数及其值的集合。
     * @return
     */
    private static List<FieldOption> getFieldOptionItemsBy(Object srcObj, String srcPropName,
                                                           Object propValue, String idProp,
                                                           boolean displayExtendInfo,
                                                           OneToMany oneToManyAnnotation,
                                                           Map<String, String[]> filterConditions) {
        List<FieldOption> options = new ArrayList<>();

        Collection<Object> objList = null;
        //如果字段值对象是集合对象,则直接处理该集合对象；
        //如果不是，则构造一个单项的列表对象。
        if (Collection.class.isAssignableFrom(propValue.getClass())) {
            objList = (Collection) propValue;
        } else {
            objList = new ArrayList<>();
            objList.add(propValue);
        }

        //对列表中的每一项提取可选项信息。
        objList.forEach(x -> {

            //如果Obj是String类型，则直接转换
            if (filterSimpleType(srcObj, srcPropName, x, x, options))
                return;

            if (idProp != null && !"id".equals(idProp)) {
                String propName = idProp;
                try {
                    Select2OptionItem[] annotations = x.getClass().getDeclaredField(idProp)
                            .getAnnotationsByType(Select2OptionItem.class);
                    if (annotations.length > 0) {
                        Select2OptionItem annotation = annotations[0];
                        propName = annotation.propName();
                    }
                } catch (Exception ignored) {
                }

                Object idValue = ReflectHelper.getFieldValue(x, idProp);
                Object textValue = ReflectHelper.getFieldValue(x, propName);
                if (filterSimpleType(srcObj, srcPropName, idValue, textValue, options))
                    return;
            }

            //针对每一项提取可选信息。
            FieldOption fieldOption = getFieldOptionItem(x, idProp, displayExtendInfo, oneToManyAnnotation, filterConditions);
            if (fieldOption != null)
                options.add(fieldOption);
        });
        return options;
    }

    /**
     * 对非集合类型的字段值提取可选项信息。
     *
     * @param obj                 字段的值对象或列表的某一项。
     * @param idProp              指定返回的FieldOption项的Value属性应该填入obj对象的哪个属性的值，默认是填入id属性的值。
     * @param displayExtendInfo   是否显示扩展信息。
     * @param oneToManyAnnotation 字段上的OneToMany注解对象。
     * @param filterConditions    前端传入参数及其值的集合。
     * @return
     */
    private static FieldOption getFieldOptionItem(Object obj, String idProp,
                                                  boolean displayExtendInfo,
                                                  OneToMany oneToManyAnnotation,
                                                  Map<String, String[]> filterConditions) {
        FieldOption fieldOption = null;

        //获取值对象obj的类上标注的Select2OptionItem注解字段集合。
        List<Field> fieldList = ReflectHelper.findAllFieldByAnnotation(obj.getClass(), Select2OptionItem.class, -1);
        if (fieldList == null || fieldList.size() <= 0)
            return null;

        //过滤掉不显示的项。
        fieldList = fieldList.stream().filter(x -> x.getAnnotation(Select2OptionItem.class).display())
                .collect(Collectors.toList());

        //处理单个且不是字符串类型的字段。
        if (fieldList.size() == 1
                && oneToManyAnnotation != null
                && fieldList.get(0).getType() != String.class) {
            //获取对象对应字段的值。
            Object value = ReflectHelper.getFieldValue(obj, fieldList.get(0).getName());
            if (value != null) {
                Field field = ReflectHelper.findFieldByName(obj.getClass(), oneToManyAnnotation.mappedBy());
                if (field != null && field.getAnnotation(ManyToOne.class) != null)
                    return getFieldOptionItem(value, idProp, displayExtendInfo, null, filterConditions);
            }
        }

        //标准标题信息
        final String[] title = {""};
        //扩展标题信息
        final String[] title1 = {""};
        final Boolean[] displayExtendInfos = {null};
        fieldList.stream()
                //.filter(x->x.getType() == String.class)
                .sorted((x, y) -> {
                    int a = x.getAnnotation(Select2OptionItem.class).order();
                    int b = y.getAnnotation(Select2OptionItem.class).order();
                    return Integer.compare(a, b);
                }).forEachOrdered(x -> {

            if (displayExtendInfos[0] != null && !displayExtendInfos[0])
                return;

            //获取对象对应字段的值。
            Object value = ReflectHelper.getFieldValue(obj, x.getName());
            if (value == null)
                return;

            Select2OptionItem annotation = x.getAnnotation(Select2OptionItem.class);
            String strValue = value.toString();
            if (value.getClass() != String.class) {
                if (value.getClass().isEnum()) {
                    strValue = EnumAnnotationParser.getTitle(value);
                } else if (value.getClass() == Boolean.class || value.getClass() == boolean.class) {
                    String[] arr = annotation.booleanConverter();
                    int index = ((boolean) value) ? 0 : 1;
                    if (index <= arr.length - 1)
                        strValue = arr[index];
                } else if (value.getClass() == BigDecimal.class) {
                    strValue = ((BigDecimal) value).setScale(4, RoundingMode.HALF_UP)
                            .stripTrailingZeros().toPlainString();
                } else {
                    String propName = annotation.propName();
                    if (!StringUtils.isEmpty(propName)) {
                        strValue = (String) ReflectHelper.getFieldValue(value, propName);
                    }
                }
            }

            //格式化值。
            if (!StringUtils.isEmpty(annotation.displayFormat()) && annotation.displayFormat().contains("%s")) {
                strValue = String.format(annotation.displayFormat(), strValue);
            }

            if (!StringUtils.isEmpty(annotation.label()) && annotation.displayLabel()) {
                strValue = MessageFormat.format("{0}:{1}", annotation.label(), strValue);
            }

            if (StringUtils.isEmpty(title[0]))
                title[0] = strValue;
            else
                title1[0] += (" " + strValue);

            if (displayExtendInfos[0] == null)
                displayExtendInfos[0] = displayExtendInfo;
        });

        if (!StringUtils.isEmpty(title1[0])) {
            title[0] = MessageFormat.format("{0} ({1})", title[0], title1[0].substring(1));
        }

        //获取可选项的父级选项
        Map<String, Object> attrs = new HashMap<>();
        List<Field> fieldAttrs = ReflectHelper.findAllFieldByAnnotation(obj.getClass(), Select2OptionAttr.class, -1);
        if (fieldAttrs != null && fieldAttrs.size() > 0) {
            fieldAttrs.stream().forEach(x -> {
                Select2OptionAttr attr = x.getAnnotation(Select2OptionAttr.class);
                try {
                    String name = attr.value();

                    Object value = null;
                    if (name.equals(x.getName()) || name.startsWith(x.getName()))
                        value = ReflectHelper.getPropObjectByPropPath(obj, name);
                    else
                        value = ReflectHelper.getPropObjectByPropPath(obj, x.getName());

                    //布尔值进行非操作。
                    if (attr.not() && (value.getClass() == boolean.class || value.getClass() == Boolean.class))
                        value = !((boolean) value);

                    if (attr.isDisabled()) {
                        try {
                            if (filterConditions.containsKey(attr.disabledControl())) {
                                String[] val = filterConditions.get(attr.disabledControl());
                                boolean control = StringHelper.convertTo(val[0], boolean.class);
                                value = ((boolean) value && control);
                            }
                        } catch (Exception ex) {
                        }
                        attrs.put("_disabled_", value);
                    } else {
                        attrs.put(name.replace('.', '_'), value);
                    }

                    if (attr.isParent() && !name.equals("parent")) {
                        attrs.put("parent", value);
                    }

                } catch (Exception ex) {
                }
            });
        }

        Object id = null;
        if (StringUtils.isEmpty(idProp))
            id = ReflectHelper.getFieldValueByAnnotation(obj, Id.class, null);
        else
            id = ReflectHelper.getFieldValue(obj, idProp);

        fieldOption = new FieldOption();
        fieldOption.setLabel(title[0]);
        fieldOption.setValue(id);
        if (attrs.containsKey("_disabled_")) {
            fieldOption.setDisabled((boolean) attrs.get("_disabled_"));
            attrs.remove("_disabled_");
        }
        fieldOption.setAttr(attrs);

        return fieldOption;
    }

    private static <T> void loadSelect2OptionValueByMappedClass(T obj,
                                                                Select2OptionValue annotation,
                                                                List<IFieldOption> fieldOptions,
                                                                List<ISelect2OptionMappedObj> select2OptionMappedObjs) {
        try {
            Class clazz = Class.forName(annotation.mappedClass());
            String idProp = StringUtils.isEmpty(annotation.mappedIdProp()) ? "id" : annotation.mappedIdProp();

            ISelect2OptionMappedObj proxy = getValidSelect2OptionMappedObj(clazz, select2OptionMappedObjs);
            if (proxy == null)
                return;

            Collection<Object> objList = new ArrayList<>();
            if (Collection.class.isAssignableFrom(obj.getClass())) {
                objList.addAll((Collection) obj);
            } else {
                objList.add(obj);
            }

            objList.forEach(m -> {
                Object proxyObject = proxy.findOneBy(idProp, m);
                //针对这个值提取字段可选项信息。
                List<FieldOption> tmpFieldOptions = getFieldOptionItemsBy(null, null, proxyObject, idProp, annotation.displayExtendInfo(), null, null);
                tmpFieldOptions.forEach(t -> {

                    if (!annotation.displayExtendInfo()) {
                        t.setLabel(t.getLabel().split("\\(")[0]);
                    }

                    //避免重复可选项
                    if (fieldOptions.stream().noneMatch(z -> Objects.equals(z.getText(), t.getText())
                            && Objects.equals(z.getId(), t.getId())))
                        fieldOptions.add(t);
                });
            });

        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
    }

    private static ISelect2OptionMappedObj getValidSelect2OptionMappedObj(Class clazz,
                                                                          List<ISelect2OptionMappedObj> select2OptionMappedObjs) {
        if (select2OptionMappedObjs == null)
            return null;

        Optional<ISelect2OptionMappedObj> optional = select2OptionMappedObjs.stream()
                .filter(x -> {
                    Type type = ((Class) (x.getClass().getGenericSuperclass())).getGenericSuperclass();
                    Class<?> tmpClass = (Class<?>) ((ParameterizedType) (type)).getActualTypeArguments()[0];
                    return clazz == tmpClass;
                }).findFirst();

        return optional.orElse(null);
    }

    private static ICustomSelect2OptionValueLoader getLoaderInstance(Object obj, String propName,
                                                                     Class<? extends CustomOptionValueLoader> converter, String packageName) {
        ICustomSelect2OptionValueLoader loader = null;
        try {
            String key = LazyLoadParser.getValidSimpleClassName(obj.getClass()) + "." + propName;
            if (CustomSelect2OptionValueLoaderMap.containsKey(key)) {
                loader = CustomSelect2OptionValueLoaderMap.get(key);
                return loader;
            }

            if (Modifier.isAbstract(converter.getModifiers())) {
                List<String> clsNames = PackageHelper.getClassName(packageName, true, true, 1);
                for (String clsName : clsNames) {
                    Class cls = Class.forName(clsName);
                    if (converter.isAssignableFrom(cls) && !Modifier.isAbstract(cls.getModifiers())) {
                        loader = (ICustomSelect2OptionValueLoader) cls.newInstance();
                        if (loader.support(obj.getClass(), propName)) {
                            CustomSelect2OptionValueLoaderMap.put(key, loader);
                            break;
                        }
                    }
                }
            } else {
                loader = converter.newInstance();
                if (loader.support(obj.getClass(), propName)) {
                    CustomSelect2OptionValueLoaderMap.put(key, loader);
                } else {
                    loader = null;
                }
            }

        } catch (Exception ex) {
        }
        return loader;
    }

    private static <T> String getPackageName(T obj, Select2OptionValue annotation) {
        String packageName = annotation.packageName();
        try {
            if (StringUtils.isEmpty(packageName)) {
                Class clazz = Class.forName(annotation.loaderClass());
                packageName = clazz.getPackage().getName();
                if (obj != null) {
                    packageName = obj.getClass().getPackage().getName();
                    int index = packageName.lastIndexOf('.');
                    if (index > 0) {
                        packageName = packageName.substring(0, index);
                    }
                }
            }
        } catch (Exception ignored) {
        }
        return packageName;
    }

    private static boolean filterSimpleType(Object srcObj, String srcPropName, Object idObj, Object valueObj, List<FieldOption> options) {
        //如果Obj是String类型，则直接转换
        if (idObj instanceof String) {
            FieldOption fieldOption = new FieldOption();
            fieldOption.setLabel((String) valueObj);
            fieldOption.setValue(idObj);
            fieldOption.setAttr(new HashMap<String, Object>() {{
                put("title", idObj);
            }});
            options.add(fieldOption);
            return true;
        } else if (idObj instanceof Enum) {

            String label = null;
            Enum enumValue = valueObj == null ? (Enum) idObj : (Enum) valueObj;
            try {
                Method method = enumValue.getClass().getMethod("getTitle", Object.class, String.class, Enum.class);
                if (method != null) {
                    label = (String) method.invoke(null, srcObj, srcPropName, enumValue);
                }
            } catch (Exception ex) {
            }

            if (StringUtils.isEmpty(label)) {
                label = EnumAnnotationParser.getTitle(enumValue);
            }

            FieldOption fieldOption = new FieldOption();
            fieldOption.setLabel(label);
            fieldOption.setValue(((Enum) idObj).name());
            fieldOption.setAttr(new HashMap<String, Object>() {{
                put("title", EnumAnnotationParser.getDescription((Enum) idObj));
            }});
            options.add(fieldOption);
            return true;
        }

        return false;
    }

}
